from distutils.util import strtobool
from ipaddress import ip_address
from json import loads
from re import fullmatch


class BooleanField(object):
    def __init__(self, default=None, required=True):
        self.default = default
        self.required = required

    def validate(self, value):
        if not self.required and value is None:
            value = self.default
        return None if value is None else bool(strtobool(value))


class CharField(object):
    def __init__(self, default=None, required=True, choices=tuple()):
        self.default = default
        self.required = required
        self.choices = choices

    def validate(self, value):
        if not self.required and value is None:
            return value or self.default
        if self.choices and value not in self.choices:
            raise ValueError(f"Must be in {self.choices}")
        return value


class EmailField(object):
    def __init__(self, default=None, required=True):
        self.default = default
        self.required = required

    def validate(self, value):
        if not self.required and value is None:
            return value or self.default
        if not fullmatch(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", value):
            raise ValueError("Must be a valid email address")
        return value


class FloatField(object):
    def __init__(self, default=None, required=True, min_value=0, max_value=0):
        self.default = default
        self.required = required
        self.min_value = min_value
        self.max_value = max_value

    def validate(self, value):
        if not self.required and value is None:
            return value or self.default
        if not value.strip("-").replace(".", "").isdigit():
            raise ValueError("Must be a valid float number")
        value = float(value)
        if self.min_value and value < self.min_value:
            raise ValueError(f"Must be greater than or equal to {self.min_value}")
        if self.max_value and value > self.max_value:
            raise ValueError(f"Must be less than or equal to {self.max_value}")
        return value


class IntegerField(object):
    def __init__(self, default=None, required=True, min_value=0, max_value=0):
        self.default = default
        self.required = required
        self.min_value = min_value
        self.max_value = max_value

    def validate(self, value):
        if not self.required and value is None:
            return value or self.default
        if not value.strip("-").isdigit():
            raise ValueError("Must be a valid integer")
        value = int(value)
        if self.min_value and value < self.min_value:
            raise ValueError(f"Must be greater than or equal to {self.min_value}")
        if self.max_value and value > self.max_value:
            raise ValueError(f"Must be less than or equal to {self.max_value}")
        return value


class IpAddressField(object):
    def __init__(self, default=None, required=True):
        self.default = default
        self.required = required

    def validate(self, value):
        if not self.required and value is None:
            value = self.default
        return ip_address(value).exploded


class JSONField(object):
    def __init__(self, default=None, required=True):
        self.default = default
        self.required = required

    def validate(self, value):
        if not self.required and value is None:
            return value or self.default
        return loads(value)
